/*=+--+=#=+--     Unmanned Aerial System Management Software      --+=#=+--+=#*\
|          Copyright (C) 2011 Regents of the University of Colorado.           |
|                             All Rights Reserved.                             |

     This program is free software: you can redistribute it and/or modify
     it under the terms of the GNU General Public License version 2 as
     published by the Free Software Foundation.

     This program is distributed in the hope that it will be useful,
     but WITHOUT ANY WARRANTY; without even the implied warranty of
     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     GNU General Public License for more details.

     You should have received a copy of the GNU General Public License
     along with this program.  If not, see <http://www.gnu.org/licenses/>.

            Jack Elston                       Cory Dixon                        
|           elstonj@colorado.edu              dixonc@colorado.edu              |
|                                                                              |
\*=+--+=#=+--                 --+=#=+--+=#=+--                    --+=#=+--+=#*/

#include "chainPage.h"
#include "vcConfig.h"

/*<---Extern Variables---->*/
extern GuiStruct  guiStr;
extern FifoMsg_t *fifo;

bool ChainPage::create_page(Client *client, GtkBuilder *builder)
{	
	chain_val = NAN;

	if( !client ) {
		cout << "ChainPage::create_page -- invalid client " << endl;
		return false;
	}
	if( page ) {
		cout << "ChainPage::create_page -- page has already been created" << endl;
		return true;
	}
	if( !builder ) {
		cout << "ChainPage::create_page -- invalid xml builder" << endl;
		return false;
	}

	page_client        = client;
	page               = GTK_WIDGET (gtk_builder_get_object(builder, "chain_page"));
	chain_node_rssi    = GTK_WIDGET (gtk_builder_get_object(builder, "chain_node_rssi"));
	chain_rssi_plot    = GTK_WIDGET (gtk_builder_get_object(builder, "chain_rssi_plot"));
	chain_kv_gain      = GTK_WIDGET (gtk_builder_get_object(builder, "chain_kv_gain"));
	chain_vmax_gain    = GTK_WIDGET (gtk_builder_get_object(builder, "chain_vmax_gain"));
	chain_gains_button = GTK_WIDGET (gtk_builder_get_object(builder, "chain_gains_button"));
	chain_nodes_button = GTK_WIDGET (gtk_builder_get_object(builder, "chain_nodes_button"));
	chain_stop_button  = GTK_WIDGET (gtk_builder_get_object(builder, "chain_stop_button"));

	chain_orbit_button = GTK_WIDGET (gtk_builder_get_object(builder, "chain_orbit_button"));;
	chain_orbit_alt    = GTK_WIDGET (gtk_builder_get_object(builder, "chain_orbit_alt"));;
	chain_orbit_rad    = GTK_WIDGET (gtk_builder_get_object(builder, "chain_orbit_rad"));
	chain_orbit_wp     = GTK_WIDGET (gtk_builder_get_object(builder, "chain_orbit_wp"));;
	chain_orbit_dir    = GTK_WIDGET (gtk_builder_get_object(builder, "chain_orbit_dir"));
	chain_mode         = GTK_WIDGET (gtk_builder_get_object(builder, "chain_mode"));

	// make sure we got all of our widgets
	if( !( page && chain_node_rssi && chain_rssi_plot && chain_kv_gain && 
	       chain_vmax_gain && chain_gains_button && chain_nodes_button && 
				 chain_mode && chain_stop_button) )
	{
		cout << "ChainPage::create_page - did not find all required widgets in xml file" << endl;	
		page = NULL;
		return false;
	}

	g_signal_connect(G_OBJECT(chain_gains_button), "clicked", G_CALLBACK(ChainPage::chain_gains_clicked), this);
	g_signal_connect(G_OBJECT(chain_orbit_button), "clicked", G_CALLBACK(ChainPage::chain_orbit_clicked), this);
	g_signal_connect(G_OBJECT(chain_nodes_button), "clicked", G_CALLBACK(ChainPage::chain_nodes_clicked), this);
	g_signal_connect(G_OBJECT(chain_stop_button), "clicked", G_CALLBACK(ChainPage::chain_stop_clicked), this);

	// rssi list
	create_node_rssi_tree();

	return true;
}

void ChainPage::update()
{
	//-- See if the page has been initialized
	if( !page_client ) {
		cout << "ChainPage::update - invalid page_client" << endl;
		return;
	}
	if( page == NULL ) {
		cout << "ChainPage::update -- page has not been created" << endl;
		return;
	}

	chain_val = page_client->telemetry.navigation.value;
	
	// don't do work if we don't need too
	if( !GTK_WIDGET_VISIBLE( page ) ) {
		return;
	}

	// update the rssi list
	update_link_list();

	// redraw page
	gtk_widget_queue_draw(page);	
}

/* columns for GtkTree */
enum
{
	IND_COLUMN = 0,
	NAME_COLUMN,
	RSSI_COLUMN,
	CHAIN_COLUMN,
	NUM_COLUMNS
};

void ChainPage::create_node_rssi_tree()
{
	// create tree store model
	list_store = gtk_list_store_new ( NUM_COLUMNS,
	                                  G_TYPE_INT,     // link ind
	                                  G_TYPE_STRING,  // name
	                                  G_TYPE_STRING,  // rssi - we convert to string ourselves
	                                  G_TYPE_BOOLEAN  // selected for linking
	                                );
	// draw the view of the tree
	format_treeview();

	// add to container
	gtk_widget_show( GTK_WIDGET(treeview) );
	gtk_container_add( GTK_CONTAINER (chain_node_rssi), GTK_WIDGET(treeview) );
}
	

void ChainPage::format_treeview()
{
	gint col_offset;
	GtkCellRenderer *renderer;

	// create tree view 
	treeview = GTK_TREE_VIEW(gtk_tree_view_new_with_model (GTK_TREE_MODEL (list_store)));

	// setup tree functionality
	gtk_tree_view_set_rules_hint(GTK_TREE_VIEW(treeview), TRUE);
	gtk_tree_view_set_grid_lines(GTK_TREE_VIEW(treeview), GTK_TREE_VIEW_GRID_LINES_BOTH);
	gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(treeview), TRUE);
	gtk_tree_view_set_headers_clickable(GTK_TREE_VIEW(treeview), TRUE);
	gtk_tree_view_set_reorderable(GTK_TREE_VIEW(treeview), TRUE);
	gtk_tree_view_set_enable_search(GTK_TREE_VIEW(treeview), FALSE);

	GtkTreeSelection *selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(treeview));
	gtk_tree_selection_set_mode( selection, GTK_SELECTION_NONE);

	// column for node slection
	renderer = gtk_cell_renderer_toggle_new ();
	g_object_set (renderer, "xalign", 0.5, NULL);
	g_object_set_data (G_OBJECT (renderer), "column", (gint *)CHAIN_COLUMN);
	col_offset = gtk_tree_view_insert_column_with_attributes (GTK_TREE_VIEW (treeview),
	      -1, "X", renderer, 
	      "active", CHAIN_COLUMN,
	      NULL);
	// connect signal for toggeling
	g_signal_connect (renderer, "toggled", G_CALLBACK (ChainPage::item_toggled), this);

	// column for node names 
	renderer = gtk_cell_renderer_text_new ();
	g_object_set (renderer, "xalign", 0.0, NULL);
	col_offset = gtk_tree_view_insert_column_with_attributes (GTK_TREE_VIEW (treeview),
			-1, "Node", renderer, 
			"text", NAME_COLUMN,
			NULL);

	// column for node rssi
	renderer = gtk_cell_renderer_text_new ();
	g_object_set (renderer, "xalign", 1.0, NULL);
	g_object_set_data (G_OBJECT (renderer), "column", (gint *)RSSI_COLUMN);

	col_offset = gtk_tree_view_insert_column_with_attributes (GTK_TREE_VIEW (treeview),
			-1, "RSSI", renderer, 
			"text", RSSI_COLUMN,
			NULL);

}

void ChainPage::update_link_list()
{
	GtkTreeIter iter;
	char idStr[32];
	char rssiStr[32];

	for(int c_ind=0; c_ind < MAX_CLIENTS; c_ind++ ) {
		if( page_client->linkstatus[c_ind].last_rx > 0 ) {

			sprintf(rssiStr,"%.1f", page_client->linkstatus[c_ind].signal);

			//-- fill in the name of the node --//
			// if we don't know ip, use mac. if ip, check
			// to see if we can get a name from the client list
			if( page_client->linkstatus[c_ind].ip[0] == 0 ) {
				sprintf(idStr,"%02X:%02X:%02X:%02X:%02X:%02X",
						page_client->linkstatus[c_ind].mac[0],
						page_client->linkstatus[c_ind].mac[1],
						page_client->linkstatus[c_ind].mac[2],
						page_client->linkstatus[c_ind].mac[3],
						page_client->linkstatus[c_ind].mac[4],
						page_client->linkstatus[c_ind].mac[5]);
			} else {
				sprintf(idStr,"%i.%i.%i.%i",
						page_client->linkstatus[c_ind].ip[0],
						page_client->linkstatus[c_ind].ip[1],
						page_client->linkstatus[c_ind].ip[2],
						page_client->linkstatus[c_ind].ip[3]);

				// check to see if we know the node name
				in_addr addr;
				addr.s_addr = *((in_addr_t *)page_client->linkstatus[c_ind].ip);
				Client * b_client = guiStr.shm_ptr->clients.getClient(addr);
				if( b_client )
					strcpy(idStr, b_client->name.c_str());
			}

			//-- walk through the list looking for the link in the tree model
			// if empty, just fill in new value
			bool valid = gtk_tree_model_get_iter_first( GTK_TREE_MODEL(list_store), &iter);
			while( valid ) {
				int link_ind;
				gtk_tree_model_get (GTK_TREE_MODEL(list_store), &iter, IND_COLUMN, &link_ind, -1);
				// if this is the current link
				if( link_ind == c_ind )
					break;

				valid = gtk_tree_model_iter_next ( GTK_TREE_MODEL(list_store), &iter);
			}
			// if we still have a valid iteration, we found the current link
			// already in the model, if not add new one
			if( !valid )
				gtk_list_store_append (list_store, &iter);

			//-- fill in the gtk tree menu --//
			gtk_list_store_set (list_store, &iter,
					IND_COLUMN,  c_ind,
					NAME_COLUMN, idStr,
					RSSI_COLUMN, rssiStr,
					-1);

		}
	}
}

void ChainPage::item_toggled(GtkCellRendererToggle *cell, gchar *path_str, gpointer data)
{
	ChainPage *me = (ChainPage *)data;
	if( !me )
		return;

	GtkTreePath *path = gtk_tree_path_new_from_string (path_str);
	GtkTreeIter iter;

	int onOff = 0;

	//cout << "ChainPage::item_toggled: path=" << path_str << endl;

	// get toggled iter 
	gtk_tree_model_get_iter (GTK_TREE_MODEL(me->list_store), &iter, path);

	// get data from current row
	gtk_tree_model_get (GTK_TREE_MODEL(me->list_store), &iter, CHAIN_COLUMN, &onOff, -1);

	// do something with the value
	onOff ^= 1;

	// set new value 
	gtk_list_store_set (me->list_store, &iter, CHAIN_COLUMN, onOff, -1);

	// clean up 
	gtk_tree_path_free (path);
}

//-----------------------------------------------------------
// button handlers -- we pass a WindowStruct pointer
void ChainPage::chain_gains_clicked(GtkButton *button, gpointer data)
{
	//cout << "ChainPage::chain_gains_clicked" << endl;
	
	ChainPage *chain = (ChainPage *)data;
	if( !chain )
		return;

	// get the goto waypoint value
	chain->gains.kv = gtk_spin_button_get_value(GTK_SPIN_BUTTON(chain->chain_kv_gain));
	chain->gains.vmax = gtk_spin_button_get_value(GTK_SPIN_BUTTON(chain->chain_vmax_gain));

	if( gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(chain->chain_mode)) )
		chain->gains.mode = 1;
	else
		chain->gains.mode = 0;

	//cout << "kv=" << chain->gains.kv << " vmax=" << chain->gains.vmax << " mode=" << chain->gains.mode << endl;
	chain->send_gains_cmd();
	chain->set_sent_state(GTK_WIDGET(button));

	return;
}

void ChainPage::chain_orbit_clicked(GtkButton *button, gpointer data)
{
	//cout << "ChainPage::chain_orbit_clicked" << endl;
	
	ChainPage *chain = (ChainPage *)data;
	if( !chain )
		return;

	// get the goto waypoint value
	chain->orbit.rad = gtk_spin_button_get_value(GTK_SPIN_BUTTON(chain->chain_orbit_rad));
	chain->orbit.alt = gtk_spin_button_get_value(GTK_SPIN_BUTTON(chain->chain_orbit_alt));
	chain->orbit.wp  = (uint8_t)gtk_spin_button_get_value(GTK_SPIN_BUTTON(chain->chain_orbit_wp));
	if( gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(chain->chain_orbit_dir)) )
		chain->orbit.dir = -1;
	else
		chain->orbit.dir = 1;

	//cout << "rad=" << chain->orbit.rad << " alt=" << chain->orbit.alt 
	     //<< " wp=" << chain->orbit.wp << " dir=" << chain->orbit.dir << endl;

	chain->send_orbit_cmd();
	chain->set_sent_state(GTK_WIDGET(button));

	return;
}

void ChainPage::chain_nodes_clicked(GtkButton *button, gpointer data)
{
	//cout << "ChainPage::chain_nodes_clicked" << endl;
	
	ChainPage *chain = (ChainPage *)data;
	if( !chain )
		return;

	chain->send_chain_cmd();
	chain->set_sent_state(GTK_WIDGET(button));

	return;
}

void ChainPage::chain_stop_clicked(GtkButton *widget, gpointer data)
{
	//cout << "ChainPage::chain_stop_clicked" << endl;
	
	ChainPage *chain = (ChainPage *)data;
	if( !chain )
		return;

	chain->send_stop_chain_cmd();

	return;
}

void ChainPage::send_gains_cmd()
{
	Client *me        = guiStr.shm_ptr->clients.getClient(0);
	fifo->src.s_addr = guiStr.shm_ptr->clients.getAddr(me, page_client).s_addr;
	fifo->dest.s_addr = guiStr.shm_ptr->clients.getAddr(page_client).s_addr;

	CopyStructToBuf( gains, CMD_DATAPTR(fifo->data));

	CMD_CMD(fifo->data)  = CMD_CHAIN_GAINS;
	CMD_NUM(fifo->data)  = sizeof( ChainGains_t );;
	CMD_CSUM(fifo->data) = 0;
	CMD_CSUM(fifo->data) = C_calcCheckSum((uint8_t*)fifo->data);

	fifo->size = CMD_SIZE(fifo->data);
	guiStr.vcgui->writeFifo(ThreadedPipe::INBOARD_TX);

	printf("<-- Sent %s CMD_CHAIN_GAINS [%s] -->\n", page_client->name.c_str(), inet_ntoa(fifo->dest));	
}

void ChainPage::send_orbit_cmd()
{
	Client *me        = guiStr.shm_ptr->clients.getClient(0);
	fifo->src.s_addr  = guiStr.shm_ptr->clients.getAddr(me, page_client).s_addr;
	fifo->dest.s_addr = guiStr.shm_ptr->clients.getAddr(page_client).s_addr;

	CopyStructToBuf( orbit, CMD_DATAPTR(fifo->data));

	CMD_CMD(fifo->data)  = CMD_ORBIT_VALUES;
	CMD_NUM(fifo->data)  = sizeof( OrbitValues_t );
	CMD_CSUM(fifo->data) = 0;
	CMD_CSUM(fifo->data) = C_calcCheckSum((uint8_t*)fifo->data);

	fifo->size = CMD_SIZE(fifo->data);
	guiStr.vcgui->writeFifo(ThreadedPipe::INBOARD_TX);

	printf("<-- Sent %s CMD_ORBIT_VALUES [%s] -->\n", page_client->name.c_str(), inet_ntoa(fifo->dest));	
}

void ChainPage::send_chain_cmd()
{	
	GtkTreeIter iter;

	Client * me       = guiStr.shm_ptr->clients.getClient(0);
	uint8_t * dataPtr = CMD_DATAPTR(fifo->data);

	fifo->src.s_addr = guiStr.shm_ptr->clients.getAddr(me, page_client).s_addr;
	fifo->dest.s_addr = guiStr.shm_ptr->clients.getAddr(page_client).s_addr;

	int payload_size=0;
	int num_chain = 0;
	VehicleInfo_t * vehicle;

	const gchar * setNames[1] = {"Chain Value"};
	void  * setPtr[1];
	int     setType[1];
	
	//cout << "ChainPage::send_chain_cmd()-- chaining on " << page_client->name << endl;

	//-- walk through the list looking for any selected nodes
	bool valid = gtk_tree_model_get_iter_first( GTK_TREE_MODEL(list_store), &iter);
	while( valid ) {
		int selected;
		int link_ind;
		gchar *name;
		gtk_tree_model_get (GTK_TREE_MODEL(list_store), &iter, 
		                    IND_COLUMN, &link_ind, 
	                       CHAIN_COLUMN, &selected, 
								  NAME_COLUMN, &name, -1);

		// if this node is selected, then send its mac address. We use 
		// mac addresses since there is no name mangling that can happen
		// or we see an ip that the node does not have yet
		if( selected ) {
			payload_size += sizeof(VehicleInfo_t);
			vehicle = (VehicleInfo_t *)dataPtr;
			
			char idStr[32];
			sprintf(idStr,"%02X:%02X:%02X:%02X:%02X:%02X",
					page_client->linkstatus[link_ind].mac[0],
					page_client->linkstatus[link_ind].mac[1],
					page_client->linkstatus[link_ind].mac[2],
					page_client->linkstatus[link_ind].mac[3],
					page_client->linkstatus[link_ind].mac[4],
					page_client->linkstatus[link_ind].mac[5]);

			strcpy(vehicle->id, idStr);
			cout << "\t name: " << name << " mac: " << vehicle->id << endl;

			dataPtr += sizeof(VehicleInfo_t);
			num_chain++;
		}
		g_free(name);

		// get next node
		valid = gtk_tree_model_iter_next ( GTK_TREE_MODEL(list_store), &iter);
	}

	chain_val   = NAN;
	setType[0]  = FLOAT_T;
	setPtr[0]   = &(chain_val);
	//strcpy( setNames[0], "Chain Value");

	if( num_chain > 0 ) {
		num_chain = 1;

		//-- RSSI plot 
		if( rssi_graph ) 
			gtk_container_remove(GTK_CONTAINER(chain_rssi_plot), rssi_graph->main_widget);
		rssi_graph = NULL;

		String chart_name = page_client->name;
		chart_name += "/";
		chart_name += "RSSI";

		rssi_graph = gtkstripchart_new(num_chain, setNames, setPtr, setType, chart_name.c_str());
		rssi_graph->parent = chain_rssi_plot;

		gtk_container_add (GTK_CONTAINER (chain_rssi_plot), rssi_graph->main_widget);
		gtk_container_set_border_width (GTK_CONTAINER (rssi_graph->main_widget), GNOME_PAD_SMALL);
		gtkstripchart_start (rssi_graph);

		//-- send CMD_CHAIN
		CMD_CMD(fifo->data)  = CMD_CHAIN;
		CMD_NUM(fifo->data)  = payload_size;
		CMD_CSUM(fifo->data) = 0;
		CMD_CSUM(fifo->data) = C_calcCheckSum((uint8_t*)fifo->data);

		fifo->size = CMD_SIZE(fifo->data);
		guiStr.vcgui->writeFifo(ThreadedPipe::INBOARD_TX);

		printf("<-- Sent %s CMD_CHAIN -->\n", page_client->name.c_str());	
	}
}

void ChainPage::send_stop_chain_cmd()
{	
	Client * me       = guiStr.shm_ptr->clients.getClient(0);

	fifo->src.s_addr = guiStr.shm_ptr->clients.getAddr(me, page_client).s_addr;
	fifo->dest.s_addr = guiStr.shm_ptr->clients.getAddr(page_client).s_addr;

	CMD_CMD(fifo->data)  = CMD_CHAIN;
	CMD_NUM(fifo->data)  = 0;
	CMD_CSUM(fifo->data) = 0;
	CMD_CSUM(fifo->data) = C_calcCheckSum((uint8_t*)fifo->data);

	fifo->size = CMD_SIZE(fifo->data);
	guiStr.vcgui->writeFifo(ThreadedPipe::INBOARD_TX);

	//-- RSSI plot 
	if( rssi_graph ) 
			gtk_container_remove(GTK_CONTAINER(chain_rssi_plot), rssi_graph->main_widget);
	rssi_graph = NULL;
	chain_val = NAN;

	printf("<-- Sent %s CMD_CHAIN -- STOP -->\n", page_client->name.c_str());	
}

// use this function to indicate a command or ACK has been received
void ChainPage::setCMD_RX(uint8_t cmd_id, int ack_nack)
{
	//cout << "ChainPage::setCMD_RX - [" << (int)cmd_id << "]  ackNack=" << ack_nack << endl;
	
	switch( cmd_id ) {
		case CMD_CHAIN:
			set_rx_state(chain_nodes_button, ack_nack);
			break;
		case CMD_ORBIT_VALUES:
			set_rx_state(chain_orbit_button, ack_nack);
			break;
		case CMD_CHAIN_GAINS:
			set_rx_state(chain_gains_button, ack_nack);
			break;

		default:
			break;
	}

	// update page
	if( page )
		gtk_widget_queue_draw(page);
}
